<?php

class keke_error_handler {
	/**
	 * @var integer maximum number source code lines to be displayed. Defaults to 25.
	 */
	public $maxSourceLines = 10;
	/**
	 * @var string the application administrator information (could be a name or email link). It is displayed in error pages to end users. Defaults to 'the webmaster'.
	 */
	public $adminInfo = 'keke teach';
	/**
	 * @var boolean whether to discard any existing page output before error display. Defaults to true.
	 */
	public $discardOutput = false;
	/**
	 * @var string the route (e.g. 'site/error') to the controller action that will be used to display external errors.
	 * Inside the action, it can retrieve the error information by Yii::app()->errorHandler->error.
	 * This property defaults to null, meaning CErrorHandler will handle the error display.
	 * @since 1.0.6
	 */
	public $errorAction;
	
	private $_error;
	
	/**
	 * Handles the exception/error event.
	 * This method is invoked by the application whenever it captures
	 * an exception or PHP error.
	 * @param CEvent the event containing the exception/error information
	 */
	public function handle($event) {
		// set event as handled to prevent it from being handled by other event handlers
		
		//$event->handled = true;
		if ($this->discardOutput) {
			while ( @ob_end_clean () )
				;
		}
		if ($event instanceof keke_exceptionEvent){ 
			 
			$this->handleException ( $event->exception );
		}else{ 
			$this->handleError ( $event );
		}
	}
	public function init_handle(){
		restore_error_handler ();
		restore_exception_handler ();
		set_error_handler( array ($this, 'handleError' ), error_reporting () );
		 // set_exception_handler(array($this,'handle'));
		 //set_exception_handler(array($this,'handleException'));
	}
	
	/**
	 * Returns the details about the error that is currently being handled.
	 * The error is returned in terms of an array, with the following information:
	 * <ul>
	 * <li>code - the HTTP status code (e.g. 403, 500)</li>
	 * <li>type - the error type (e.g. 'CHttpException', 'PHP Error')</li>
	 * <li>message - the error message</li>
	 * <li>file - the name of the PHP script file where the error occurs</li>
	 * <li>line - the line number of the code where the error occurs</li>
	 * <li>trace - the call stack of the error</li>
	 * <li>source - the context source code where the error occurs</li>
	 * </ul>
	 * @return array the error details. Null if there is no error.
	 * @since 1.0.6
	 */
	public function getError() {
		return $this->_error;
	}
	
	/**
	 * Handles the exception.
	 * @param Exception the exception captured
	 */
	public function handleException($exception) { 
		global $_K;
		$app = null;
		if ($app instanceof keke_exception) {
			if (($trace = $this->getExactTrace ( $exception )) === null) {
				$fileName = $exception->getFile ();
				$errorLine = $exception->getLine ();
			} else {
				$fileName = $trace ['file'];
				$errorLine = $trace ['line'];
			}
			$this->_error = $data = array ('code' => ($exception instanceof keke_exception) ? $exception->statusCode : 500, 'type' => get_class ( $exception ), 'errorCode' => $exception->getCode (), 'message' => $exception->getMessage (), 'file' => $fileName, 'line' => $errorLine, 'trace' => $exception->getTraceAsString (), 'source' => $this->getSourceLines ( $fileName, $errorLine ) );
			
			if (! headers_sent ())
				header ( "HTTP/1.0 {$data['code']} " . get_class ( $exception ) );
			if ($exception instanceof keke_exception || ! $_K ['is_debug']) {
				$this->render ( 'exception', $data );
			} else {
				$this->render ( 'error', $data );
			}
		} else
			$this->displayException ( $exception );
	}
	
	/**
	 * Handles the PHP error.
	 * @param CErrorEvent the PHP error event
	 */
	public function handleError($code, $message, $file, $line) { 
		global $_K;
		$trace = debug_backtrace ();
		$event = null;
		$event->code = $code;
		$event->message = $message;
		$event->file = $file;
		$event->line = $line;
		//debug_print_backtrace();
		// skip the first 3 stacks as they do not tell the error position
		if (count ( $trace ) > 3)
			$trace = array_slice ( $trace, 3 );
		$traceString = '';
		foreach ( $trace as $i => $t ) {
			if (! isset ( $t ['file'] ))
				$t ['file'] = 'unknown';
			if (! isset ( $t ['line'] ))
				$t ['line'] = 0;
			if (! isset ( $t ['function'] ))
				$t ['function'] = 'unknown';
			$traceString .= "#$i {$t['file']}({$t['line']}): ";
			if (isset ( $t ['object'] ) && is_object ( $t ['object'] ))
				$traceString .= get_class ( $t ['object'] ) . '->';
			$traceString .= "{$t['function']}()\n";
		}
		
		if ($event) {
			$this->_error = $data = array ('code' => $code, 'type' => 'PHP Error', 'message' => $event->message, 'file' => $event->file, 'line' => $event->line, 'trace' => $traceString, 'source' => $this->getSourceLines ( $event->file, $event->line ) );
			
			if (! headers_sent ())
				header ( "HTTP/1.0 500 PHP Error" );
			if ($_K ['is_debug']) {
				$this->render ( 'exception', $data );
			}else
				$this->render ( 'error', $data );
		} else
			$this->displayError ( $event->code, $event->message, $event->file, $event->line );
	}
	
	/**
	 * @param Exception the uncaught exception
	 * @return array the exact trace where the problem occurs
	 */
	protected function getExactTrace($exception) {
		$traces = $exception->getTrace ();
		
		foreach ( $traces as $trace ) {
			// property access exception
			if (isset ( $trace ['function'] ) && ($trace ['function'] === '__get' || $trace ['function'] === '__set'))
				return $trace;
		}
		return null;
	}
	
	/**
	 * Renders the view.
	 * @param string the view name (file name without extension).
	 * See {@link getViewFile} for how a view file is located given its name.
	 * @param array data to be passed to the view
	 */
	protected function render($view, $data) {
		 
		if ($view == 'error') {
			kekezu::show_error($data);
			die();
		}else { 
			$data ['version'] = $this->getVersionInfo ();
			$data ['time'] = time ();
			$data ['admin'] = $this->adminInfo;
			kekezu::show_error($data);
			die();
		}
	}
	protected function get_content($data) {
		if ($data) {
			$content = "<pre>";
			foreach ( $data as $k => $v ) {
				$content .= $k . ":" . $v . "</br>";
			}
		}
		return $content."</pre>";
	}
	
	
	/**
	 * @return string server version information. If the application is in production mode, nothing is returned.
	 */
	protected function getVersionInfo() {
		global $_K;
		if ($_K['is_debug']) {
			$version = '<a href="http://www.kekezu.com/">kekezu Framework</a>/' . KEKE_VERSION;
			if (isset ( $_SERVER ['SERVER_SOFTWARE'] ))
				$version = $_SERVER ['SERVER_SOFTWARE'] . ' ' . $version;
		} else
			$version = '';
		return $version;
	}
	
	/**
	 * Returns the source lines around the error line.
	 * At most {@link maxSourceLines} lines will be returned.
	 * @param string source file path
	 * @param integer the error line number
	 * @return array source lines around the error line, indxed by line numbers
	 */
	protected function getSourceLines($file, $line) {
		// determine the max number of lines to display
		$maxLines = $this->maxSourceLines;
		if ($maxLines < 1)
			$maxLines = 1;
		else if ($maxLines > 100)
			$maxLines = 100;
		
		$line --; // adjust line number to 0-based from 1-based
		if ($line < 0 || ($lines = @file ( $file )) === false || ($lineCount = count ( $lines )) <= $line)
			return array ();
		
		$halfLines = ( int ) ($maxLines / 2);
		$beginLine = $line - $halfLines > 0 ? $line - $halfLines : 0;
		$endLine = $line + $halfLines < $lineCount ? $line + $halfLines : $lineCount - 1;
		
		$sourceLines = array ();
		for($i = $beginLine; $i <= $endLine; ++ $i)
			$sourceLines [$i + 1] = $lines [$i];
		return $sourceLines;
	}
	public function displayError($code, $message, $file, $line) {
		global $_K;
		if ($_K['is_debug']) {
			echo "<h1>PHP Error [$code]</h1>\n";
			echo "<p>$message ($file:$line)</p>\n";
			echo '<pre>';
			debug_print_backtrace ();
			echo '</pre>';
		} else {
			echo "<h1>PHP Error [$code]</h1>\n";
			echo "<p>$message</p>\n";
		}
	}
	public function displayException($exception) {
		global $_K;
		if ($_K['is_debug']) {
			echo '<h1>' . get_class ( $exception ) . "</h1>\n";
			echo '<p>' . $exception->getMessage () . ' (' . $exception->getFile () . ':' . $exception->getLine () . ')</p>';
			echo '<pre>' . $exception->getTraceAsString () . '</pre>';
		} else {
			echo '<h1>' . get_class ( $exception ) . "</h1>\n";
			echo '<p>' . $exception->getMessage () . '</p>';
		}
	}
}

class keke_exception extends Exception {

}
class keke_event extends keke_component
{
	/**
	 * @var object the sender of this event
	 */
	public $sender;
	/**
	 * @var boolean whether the event is handled. Defaults to false.
	 * When a handler sets this true, the rest uninvoked handlers will not be invoked anymore.
	 */
	public $handled=false;

	/**
	 * Constructor.
	 * @param mixed sender of the event
	 */
	public function __construct($sender=null)
	{
		$this->sender=$sender;
	}
}
class keke_exceptionEvent extends keke_event
{
	/**
	 * @var CException the exception that this event is about.
	 */
	public $exception;

	/**
	 * Constructor.
	 * @param mixed sender of the event
	 * @param CException the exception
	 */
	public function __construct($sender,$exception)
	{
		$this->exception=$exception;
		parent::__construct($sender);
	}
}
class keke_errorEvent extends keke_event
{
	/**
	 * @var string error code
	 */
	public $code;
	/**
	 * @var string error message
	 */
	public $message;
	/**
	 * @var string error message
	 */
	public $file;
	/**
	 * @var string error file
	 */
	public $line;

	/**
	 * Constructor.
	 * @param mixed sender of the event
	 * @param string error code
	 * @param string error message
	 * @param string error file
	 * @param integer error line
	 */
	public function __construct($sender,$code,$message,$file,$line)
	{
		$this->code=$code;
		$this->message=$message;
		$this->file=$file;
		$this->line=$line;
		parent::__construct($sender);
	}
}
class keke_component
{
	private $_e;
	private $_m;
	public function __get($name)
	{
		$getter='get'.$name;
		if(method_exists($this,$getter))
			return $this->$getter();
		else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
		{
			// duplicating getEventHandlers() here for performance
			$name=strtolower($name);
			if(!isset($this->_e[$name]))
				$this->_e[$name]=NULL;
			return $this->_e[$name];
		}
		else if(isset($this->_m[$name]))
			return $this->_m[$name];
		else if(is_array($this->_m))
		{
			foreach($this->_m as $object)
			{
				if($object->getEnabled() && (property_exists($object,$name) || $object->canGetProperty($name)))
					return $object->$name;
			}
		}
		throw new keke_exception('Property "{class}.{property}" is not defined.',
			array('{class}'=>get_class($this), '{property}'=>$name));
	}
	public function __set($name,$value)
	{
		$setter='set'.$name;
		if(method_exists($this,$setter))
			return $this->$setter($value);
		else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
		{
			// duplicating getEventHandlers() here for performance
			$name=strtolower($name);
			if(!isset($this->_e[$name]))
				$this->_e[$name]=null;
			return $this->_e[$name]->add($value);
		}
		else if(is_array($this->_m))
		{
			foreach($this->_m as $object)
			{
				if($object->getEnabled() && (property_exists($object,$name) || $object->canSetProperty($name)))
					return $object->$name=$value;
			}
		}
		if(method_exists($this,'get'.$name))
			throw new keke_exception('Property "{class}.{property}" is read only.',
				array('{class}'=>get_class($this), '{property}'=>$name));
		else
			throw new keke_exception('Property "{class}.{property}" is not defined.',
				array('{class}'=>get_class($this), '{property}'=>$name));
	}
	public function __isset($name)
	{
		$getter='get'.$name;
		if(method_exists($this,$getter))
			return $this->$getter()!==null;
		else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
		{
			$name=strtolower($name);
			return isset($this->_e[$name]) && $this->_e[$name]->getCount();
		}
		else if(is_array($this->_m))
		{
 			if(isset($this->_m[$name]))
 				return true;
			foreach($this->_m as $object)
			{
				if($object->getEnabled() && (property_exists($object,$name) || $object->canGetProperty($name)))
					return true;
			}
		}
		return false;
	}
	public function __unset($name)
	{
		$setter='set'.$name;
		if(method_exists($this,$setter))
			$this->$setter(null);
		else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
			unset($this->_e[strtolower($name)]);
		else if(is_array($this->_m))
		{
			if(isset($this->_m[$name]))
				$this->detachBehavior($name);
			else
			{
				foreach($this->_m as $object)
				{
					if($object->getEnabled())
					{
						if(property_exists($object,$name))
							return $object->$name=null;
						else if($object->canSetProperty($name))
							return $object->$setter(null);
					}
				}
			}
		}
		else if(method_exists($this,'get'.$name))
			throw new keke_exception('Property "{class}.{property}" is read only.',
				array('{class}'=>get_class($this), '{property}'=>$name));
	}
	public function __call($name,$parameters)
	{
		if($this->_m!==null)
		{
			foreach($this->_m as $object)
			{
				if($object->getEnabled() && method_exists($object,$name))
					return call_user_func_array(array($object,$name),$parameters);
			}
		}
		if(class_exists('Closure', false) )
			return call_user_func_array($this->$name, $parameters);
		throw new keke_exception('{class} does not have a method named "{name}".',
			array('{class}'=>get_class($this), '{name}'=>$name));
	}
	public function asa($behavior)
	{
		return isset($this->_m[$behavior]) ? $this->_m[$behavior] : null;
	}
	public function attachBehaviors($behaviors)
	{
		foreach($behaviors as $name=>$behavior)
			$this->attachBehavior($name,$behavior);
	}
	public function detachBehaviors()
	{
		if($this->_m!==null)
		{
			foreach($this->_m as $name=>$behavior)
				$this->detachBehavior($name);
			$this->_m=null;
		}
	}
	public function attachBehavior($name,$behavior)
	{
		//if(!($behavior instanceof IBehavior))
			//$behavior=Yii::createComponent($behavior);
		$behavior->setEnabled(true);
		$behavior->attach($this);
		return $this->_m[$name]=$behavior;
	}
	public function detachBehavior($name)
	{
		if(isset($this->_m[$name]))
		{
			$this->_m[$name]->detach($this);
			$behavior=$this->_m[$name];
			unset($this->_m[$name]);
			return $behavior;
		}
	}
	public function enableBehaviors()
	{
		if($this->_m!==null)
		{
			foreach($this->_m as $behavior)
				$behavior->setEnabled(true);
		}
	}
	public function disableBehaviors()
	{
		if($this->_m!==null)
		{
			foreach($this->_m as $behavior)
				$behavior->setEnabled(false);
		}
	}
	public function enableBehavior($name)
	{
		if(isset($this->_m[$name]))
			$this->_m[$name]->setEnabled(true);
	}
	public function disableBehavior($name)
	{
		if(isset($this->_m[$name]))
			$this->_m[$name]->setEnabled(false);
	}
	public function hasProperty($name)
	{
		return method_exists($this,'get'.$name) || method_exists($this,'set'.$name);
	}
	public function canGetProperty($name)
	{
		return method_exists($this,'get'.$name);
	}
	public function canSetProperty($name)
	{
		return method_exists($this,'set'.$name);
	}
	public function hasEvent($name)
	{
		return !strncasecmp($name,'on',2) && method_exists($this,$name);
	}
	public function hasEventHandler($name)
	{
		$name=strtolower($name);
		return isset($this->_e[$name]) && $this->_e[$name]->getCount()>0;
	}
	public function getEventHandlers($name)
	{
		if($this->hasEvent($name))
		{
			$name=strtolower($name);
			if(!isset($this->_e[$name]))
				$this->_e[$name]= null;
			return $this->_e[$name];
		}
		else
			throw new keke_exception('Event "{class}.{event}" is not defined.',
				array('{class}'=>get_class($this), '{event}'=>$name));
	}
	public function attachEventHandler($name,$handler)
	{
		$this->getEventHandlers($name)->add($handler);
	}
	public function detachEventHandler($name,$handler)
	{
		if($this->hasEventHandler($name))
			return $this->getEventHandlers($name)->remove($handler)!==false;
		else
			return false;
	}
	public function raiseEvent($name,$event)
	{
		global $_K;
		$name=strtolower($name);
		if(isset($this->_e[$name]))
		{
			foreach($this->_e[$name] as $handler)
			{
				if(is_string($handler))
					call_user_func($handler,$event);
				else if(is_callable($handler,true))
				{
					if(is_array($handler))
					{
						// an array: 0 - object, 1 - method name
						list($object,$method)=$handler;
						if(is_string($object))	// static method call
							call_user_func($handler,$event);
						else if(method_exists($object,$method))
							$object->$method($event);
						else
							throw new keke_exception('Event "{class}.{event}" is attached with an invalid handler "{handler}".',
								array('{class}'=>get_class($this), '{event}'=>$name, '{handler}'=>$handler[1]));
					}
					else // PHP 5.3: anonymous function
						call_user_func($handler,$event);
				}
				else
					throw new keke_exception('Event "{class}.{event}" is attached with an invalid handler "{handler}".',
						array('{class}'=>get_class($this), '{event}'=>$name, '{handler}'=>gettype($handler)));
				// stop further handling if param.handled is set true
				if(($event instanceof keke_event) && $event->handled)
					return;
			}
		}
		else if($_K['is_debug'] && !$this->hasEvent($name))
			throw new keke_exception('Event "{class}.{event}" is not defined.',
				array('{class}'=>get_class($this), '{event}'=>$name));
	}
	public function evaluateExpression($_expression_,$_data_=array())
	{
		if(is_string($_expression_))
		{
			extract($_data_);
			return eval('return '.$_expression_.';');
		}
		else
		{
			$_data_[]=$this;
			return call_user_func_array($_expression_, $_data_);
		}
	}
}
?>